import { Vector3, Matrix4 } from "../WebGL";
import { assignBonds } from "./utils/assignBonds";
import { anumToSymbol } from "./utils/anumToSymbol";
import { ParserOptionsSpec } from "./ParserOptionsSpec";
import { AtomSpec, Cryst } from "specs";

/**
 * @param {string}
 *            str
 * @param {ParserOptionsSpec}
 *            options
 * @category Parsers
 */
export function CUBE(str: string, options: ParserOptionsSpec) {
  options = options || {};
  const atoms: Array<AtomSpec[]> & { modelData?: unknown } = [[]];
  let lines = str.split(/\r?\n/);
  const assignbonds =
    options.assignBonds === undefined ? true : options.assignBonds;

  if (lines.length < 6) return atoms;

  let lineArr = lines[2].replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");

  const natoms = Math.abs(parseFloat(lineArr[0]));

  let cryst: Omit<Cryst, "a" | "b" | "c" | "alpha" | "beta" | "gamma"> = {
    origin: undefined,
    size: undefined,
    unit: undefined,
    matrix4: undefined,
    matrix: undefined,
  };

  const origin = (cryst.origin = new Vector3(
    parseFloat(lineArr[1]),
    parseFloat(lineArr[2]),
    parseFloat(lineArr[3])
  ));

  lineArr = lines[3].replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");
  lineArr = lines[3].replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");

  // might have to convert from bohr units to angstroms
  // there is a great deal of confusion here:
  // n>0 means angstroms: http://www.gaussian.com/g_tech/g_ur/u_cubegen.htm
  // n<0 means angstroms: http://paulbourke.net/dataformats/cube/
  // always assume bohr: openbabel source code
  // always assume angstrom: http://www.ks.uiuc.edu/Research/vmd/plugins/molfile/cubeplugin.html
  // we are going to go with n<0 means angstrom - note this is just the first n
  const convFactor = (lineArr[0] as any) > 0 ? 0.529177 : 1;
  origin.multiplyScalar(convFactor);

  const nX = Math.abs(lineArr[0] as any);
  const xVec = new Vector3(
    parseFloat(lineArr[1]),
    parseFloat(lineArr[2]),
    parseFloat(lineArr[3])
  ).multiplyScalar(convFactor);

  lineArr = lines[4].replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");
  const nY = Math.abs(lineArr[0] as any);
  const yVec = new Vector3(
    parseFloat(lineArr[1]),
    parseFloat(lineArr[2]),
    parseFloat(lineArr[3])
  ).multiplyScalar(convFactor);

  lineArr = lines[5].replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");
  const nZ = Math.abs(lineArr[0] as any);
  const zVec = new Vector3(
    parseFloat(lineArr[1]),
    parseFloat(lineArr[2]),
    parseFloat(lineArr[3])
  ).multiplyScalar(convFactor);

  cryst.size = { x: nX, y: nY, z: nZ };
  cryst.unit = new Vector3(xVec.x, yVec.y, zVec.z);

  if (
    xVec.y != 0 ||
    xVec.z != 0 ||
    yVec.x != 0 ||
    yVec.z != 0 ||
    zVec.x != 0 ||
    zVec.y != 0
  ) {
    //need a transformation matrix
    cryst.matrix4 = new Matrix4(
      xVec.x,
      yVec.x,
      zVec.x,
      0,
      xVec.y,
      yVec.y,
      zVec.y,
      0,
      xVec.z,
      yVec.z,
      zVec.z,
      0,
      0,
      0,
      0,
      1
    );
    // include translation in matrix
    let t = new Matrix4().makeTranslation(origin.x, origin.y, origin.z);
    cryst.matrix4 = cryst.matrix4.multiplyMatrices(t, cryst.matrix4);
    cryst.matrix = cryst.matrix4.matrix3FromTopLeft();
    // all translation and scaling done by matrix, so reset origin and unit
    cryst.origin = new Vector3(0, 0, 0);
    cryst.unit = new Vector3(1, 1, 1);
  }

  atoms.modelData = [{ cryst: cryst }];

  // Extract atom portion; send to new GLModel...
  lines = lines.splice(6, natoms);

  var start = atoms[atoms.length - 1].length;
  var end = start + lines.length;

  for (var i = start; i < end; ++i) {
    var atom: Record<string, any> = {};
    atom.serial = i;
    var line = lines[i - start];
    var tokens = line.replace(/^\s+/, "").replace(/\s+/g, " ").split(" ");
    atom.elem = anumToSymbol[tokens[0]];
    atom.x = parseFloat(tokens[2]) * convFactor;
    atom.y = parseFloat(tokens[3]) * convFactor;
    atom.z = parseFloat(tokens[4]) * convFactor;

    atom.hetflag = true;
    atom.bonds = [];
    atom.bondOrder = [];
    atom.properties = {};
    atoms[atoms.length - 1].push(atom);
  }

  if (assignbonds) {
    for (let i = 0; i < atoms.length; i++) assignBonds(atoms[i], options);
  }
  return atoms;
}
